﻿using System;
namespace BinaryXmlInspector
{
    /// <summary>
    /// Bare bones iterator for strings.  Combines concepts from CharEnumerator and StringReader that were missing or
    /// I felt were too lame (even more lame than this class) as implemented.
    /// </summary>
    public class StringIterator
    {
        public StringIterator(string _s)
        {
            s = _s;
        }        
        private string s;

        public int Index
        {
            get;
            private set;
        }

        public bool EOF
        {
            get { return BadOffset(0); }
        }
                
        public char? Current
        {
            get 
            { 
                if (EOF)
                    return null;
                else
                    return s[Index]; 
            }
        }

        public char? Peek
        {
            get
            {
                if (BadOffset(1))
                    return null;
                else
                    return s[Index + 1];
            }
        }

        public void MoveNext()
        {
            ++Index;
        }

        /// <summary>
        /// Read the next N characters
        /// </summary>
        public string Read(int chars)
        {
            var oldIndex = Index;
            Index += chars;
            return EOF ? s.Substring(oldIndex) : s.Substring(oldIndex, chars);
        }

        /// <summary>
        /// Read the stream as long as the condition is true, or until EOF is hit.  Return the characters read up 
        /// to (but not including) that point.  Postcondition: the character @ Index doesn't match the predicate.
        /// </summary>
        public string ReadWhile(Func<char, bool> predicate)
        {            
            var oldIndex = Index;
            while (Current.HasValue && predicate(Current.Value))
            {
                MoveNext();
            }
            return s.Substring(oldIndex, Index - oldIndex);
        }

        /// <summary>
        /// Scan the stream until we find endToken.  Return characters read up to (but not including) that token, or null if
        /// we encounter EOF.  Postcondition: the Index points just *beyond* the endToken.
        /// </summary>
        /// <param name="endToken"></param>
        /// <returns></returns>
        public string ReadUntil(string endToken)
        {            
            var oldIndex = Index;
            while (true)
            {                
                if (BadOffset(endToken.Length - 1))
                    return null;
                if (MatchSubString(endToken))
                    break;
                MoveNext();
            }
            var tokenLen = Index - oldIndex;
            Index += endToken.Length;
            return s.Substring(oldIndex, tokenLen);
        }

        /// <summary>
        /// Returns true if 'Index + off' points to an invalid place in the stream, false otherwise
        /// </summary>
        private bool BadOffset(int off)
        {
            var idx = Index + off;
            return idx >= s.Length || idx < 0;
        }

        /// <summary>
        /// Equivalent to 'sub == s.Substring(Index, sub.Length - 1)'
        /// But should be faster since no temporary objects are created
        /// </summary>        
        private bool MatchSubString(string sub)
        {
            for (int i = 0; i < sub.Length; ++i)
            {
                if (sub[i] != s[Index + i])
                    return false;
            }
            return true;
        }     
    }
}
